package com.litong.spring.mvc.redis.dao;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class RedisBaseDao<K, V> {
  private volatile String vClassName;
  private volatile String key;
  private volatile Class<V> clazz = null;
  private String lock = "lock";
  @Value("${redis.key.prefix}")
  private String prefix;

  ObjectMapper mapper = new ObjectMapper();
  @Autowired
  StringRedisTemplate stringRedisTemplate;

  void add(V v) {
    HashOperations<String, K, String> opsForHash = getOpsForHash();
    K id = invokeGetMethod(v, "id");
    String json = writeValueAsString(v);
    opsForHash.put(getKey(), id, json);
  }

  void add(List<V> list) {
    for (V v : list) {
      add(v);
    }
  }

  void deleteById(K id) {
    HashOperations<String, K, String> opsForHash = getOpsForHash();
    opsForHash.delete(getKey(), id);
  }

  void deleteByIds(List<K> keys) {
    for (K id : keys) {
      deleteById(id);
    }
  }

  boolean updateById(K id, V e) {
    HashOperations<String, K, String> opsForHash = getOpsForHash();
    String json = writeValueAsString(e);
    opsForHash.put(getKey(), id, json);
    return false;
  }

  V getById(K id) {
    HashOperations<String, K, String> opsForHash = getOpsForHash();
    String string = opsForHash.get(getKey(), id);
    V readValue = readValue(getClazz(), string);
    return readValue;
  }

  public String getVClazzName() {
    if (vClassName == null) {
      synchronized (lock) {
        // 获取泛型
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        ParameterizedType type = (ParameterizedType) genericSuperclass;
        // 获取泛型类型名称
        Type[] types = type.getActualTypeArguments();
        // 获取名称
        vClassName = types[1].getTypeName();
      }
    }
    return vClassName;
  }

  @SuppressWarnings("unchecked")
  public Class<V> getClazz() {
    if (clazz == null) {
      synchronized (lock) {
        Type genericSuperclass = this.getClass().getGenericSuperclass();
        ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
        Type type = parameterizedType.getActualTypeArguments()[1];
        clazz = (Class<V>) type;
      }
    }
    return clazz;
  }

  private String getKey() {
    if (key == null) {
      synchronized (lock) {
        String vClazzName = getVClazzName();
        key = prefix + "_" + vClazzName.substring(vClazzName.lastIndexOf('.') + 1);
      }
    }
    return key;
  }

  @SuppressWarnings("unchecked")
  public static <T> T invokeGetMethod(Object obj, String fieldName) {
    Class<? extends Object> clazz = obj.getClass();
    PropertyDescriptor pd = null;
    try {
      pd = new PropertyDescriptor(fieldName, clazz);
    } catch (IntrospectionException e) {
      log.error("执行new PropertyDescriptor(fieldName, clazz)出现错误");
      e.printStackTrace();
    }
    Method readMethod = pd.getReadMethod();
    if (readMethod != null) {
      Object invoke = null;
      try {
        invoke = readMethod.invoke(obj);
      } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
        e.printStackTrace();
      }
      return (T) invoke;
    } else {
      return null;
    }
  }

  public String writeValueAsString(V v) {
    String json = null;
    try {
      json = mapper.writeValueAsString(v);
    } catch (JsonProcessingException e1) {
      e1.printStackTrace();
    }
    return json;
  }

  private HashOperations<String, K, String> getOpsForHash() {
    HashOperations<String, K, String> opsForHash = stringRedisTemplate.opsForHash();
    return opsForHash;
  }

  private <T> T readValue(Class<T> clazz, String string) {
    try {
      return mapper.readValue(string, clazz);
    } catch (JsonParseException e) {
      e.printStackTrace();
    } catch (JsonMappingException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return null;
  }
}